# -*- coding: iso-8859-1 -*-

class Array
  def pick
    self[ rand( self.length ) ]
  end

  def random_split
    idx = rand( self.length )

    [ self[0...idx], self[idx..-1] ]
  end

  def remove(xs)
    self.select do |x|
      xs.all? { |y| x != y }
    end
  end
end

class String
  def chars
    split //
  end

  def random(length)
    cs = chars

    1.upto(length).map { |_| cs.pick }.join
  end
end


module TeXGenerator

  class Line
    def initialize(contents, errors = [])
      @contents = contents
      @errors = errors
    end

    attr_reader :contents
    attr_reader :errors

    def to_s
      "#{if @errors.empty? then "  " else "* " end}#{@contents}"
    end
  end

  class Environment
    def initialize(tag, contents, header_errors)
      @tag = tag
      @contents = contents
      @header_errors = header_errors
    end

    attr_reader :tag
    attr_reader :contents
    attr_reader :header_errors
  end

  class LabeledEnvironment < Environment
    def initialize(tag, caption, label, contents, header_errors)
      super(tag, contents, header_errors)

      @caption = caption
      @label = label     
    end

    attr_reader :tag
    attr_reader :caption
    attr_reader :label
    attr_reader :contents

    def header
      errors = []
      caption = if @caption
                then "[#@caption]"
                else "[]"
                end

      label = if @label
              then "\\label{#{@label}}"                
              else ""
              end

      Line.new("\\begin{#{tag}}#{caption} #{label}", header_errors)
    end

    def footer
      Line.new("\\end{#{tag}}")
    end

    def lines
      yield header
      @contents.lines { |line| yield line }
      yield footer
    end
  end

  class Definition < LabeledEnvironment
    def initialize(label, contents, header_errors = [])
      caption = if rand(2) == 0
                then "abcdef".random(10)
                else nil
                end

      super("definition", caption, label, contents, header_errors)
    end
  end

  class Theorem < LabeledEnvironment
    def initialize(label, contents, header_errors = [])
      caption = if rand(2) == 0
                then "abcdef".random(10)
                else nil
                end

      super("theorem", caption, label, contents, header_errors)
    end
  end

  class TextBlock
    def self.random(refs = [])
      contents = 0.upto(refs.length * 2 + 5).map { Token.random } + refs

      TextBlock.new( contents.shuffle )
    end

    def initialize(contents)
      @contents = contents
    end

    def lines
      acc = ""
      errs = []

      @contents.each do |datum|
        acc += datum.to_s
        errs += datum.errors

        if acc.length > 50
        then
          yield Line.new(acc, errs)
          acc = ""
          errs = []
        end
      end

      yield Line.new(acc, errs) unless acc.empty? and errs.empty?
    end
  end

  class Reference
    def initialize(label, errors)
      @label = label
      @errors = errors
    end

    attr_accessor :errors

    def to_s
      "\\ref{#{@label}}"
    end
  end

  class Token
    def self.valid_char
      "blargh! ".random(1)
    end

    def self.invalid_char
      (128..255).to_a.pick.chr
    end

    def self.valid_str(length)
      1.upto(length).map { valid_char }.join
    end

    def self.valid
      str = valid_str( rand(50) )

      Token.new(str, [])
    end

    def self.invalid
      str = valid_str( rand(20) ) + invalid_char + valid_str( rand(20) )

      Token.new(str, [ :InvalidCharacterError ])
    end

    def self.random
      if rand(10) == 0
        invalid
      else
        valid
      end
    end

    def initialize(token, errors)
      @token = token
      @errors = errors
    end

    attr_accessor :errors

    def to_s
      @token
    end
  end

  class Generator
    def initialize
      @contents = []
      @nextid = 0
      @count = 0
    end

    attr_reader :contents

    def genlabel(prefix)
      @nextid += 1
      "#{prefix}:#{@nextid}"
    end

    def add_random(n = 0)
      @count += n

      while @count > 0
        [ lambda { add_no_label },
          lambda { add_random_environment },
          lambda { add_wrong_label },
          lambda { add_dup_label },
          lambda { add_forward_reference },
          lambda { add_undefined_label } ].pick.call
      end
    end

    def random_env
      [ :Definition, :Theorem ].pick
    end

    def label_prefix(type)
      case type
      when :Definition then "def"
      when :Theorem then "thm"
      end
    end

    def add_correct_env(contents = nil, type = nil)
      type = random_env unless type
      contents = TextBlock.random unless contents
      @contents << TeXGenerator.const_get(type).new(genlabel(label_prefix(type)), TextBlock.random)
      @count -= 1
    end

    def add_no_label(contents = nil, type = nil)
      type = random_env unless type
      contents = TextBlock.random unless contents
      @contents << TeXGenerator.const_get(type).new(nil, contents, [ :MissingLabelError ])
      @count -= 1
    end

    def add_wrong_label(contents = nil, type = nil)
      type = random_env unless type
      contents = TextBlock.random unless contents
      @contents << TeXGenerator.const_get(type).new(genlabel("pom".random(3)), contents, [ :InvalidLabelError ] )
      @count -= 1
    end

    def add_dup_label(type = nil)
      type = random_env unless type
      @count -= 1
      label = genlabel(label_prefix(type))
      @contents << TeXGenerator.const_get(type).new(label, TextBlock.random)
      add_random
      @contents << TeXGenerator.const_get(type).new(label, TextBlock.random, [ :DuplicateLabelError ] )
    end

    def add_forward_reference
      type = random_env
      @count -= 1
      lbl = genlabel("thm")
      lbl2 = genlabel(label_prefix(type))
      @contents << Theorem.new(lbl, TextBlock.random([ Reference.new(lbl2, [ :ForwardReferenceError ]) ]))
      add_random
      @contents << TeXGenerator.const_get(type).new(lbl2, TextBlock.random)      
    end

    def add_undefined_label(type = nil)
      type = random_env unless type
      lbl = genlabel("thm")
      @contents << TeXGenerator.const_get(type).new(genlabel(label_prefix(type)), TextBlock.random([ Reference.new(lbl, [ :UnknownLabelError ] ) ]))
      @count -= 1
    end

    def add_random_environment
      tag = "abcdefgh".random(5)
      label = genlabel( "qxyz".random(3) )
      contents = TextBlock.random

      @contents << LabeledEnvironment.new(tag, nil, label, contents, [])
    end

    def lines
      result = []
      @contents.each do |content|
        content.lines { |line| result << line }
      end
      result
    end

    def split_in_files(n)
      files = Hash.new
      files["main.tex"] = lines

      n.times do
        filename, lines = files.to_a.sort do |p1, p2|
          p2[1].length <=> p1[1].length
        end[0]
        
        xs, ys = lines.random_split
        ys, zs = ys.random_split
        
        fn = "abcdefghijklmnopqrstuvwxyz".random(10)
        extension = if rand(2) == 0
                    then ".tex"
                    else ""
                    end

        files[filename] = xs + [ Line.new("\\input{#{fn}#{extension}}") ] + zs
        files["#{fn}.tex"] = ys
      end

      files
    end

    def errors(fn, ls)
      ls.zip((1..ls.length).to_a).map do |line, n|
        pos = TeXCheck::Location.new(fn, n)
        lobj = TeXCheck::Line.new(line.contents, pos)
        
        line.errors.map do |error|          
          TeXCheck::const_get(error).new(lobj)
        end
      end.flatten
    end

    def build(nfiles = 10)                # changed
      files = split_in_files( nfiles )    # changed
      errs = files.to_a.map { |fn, ls| errors(fn, ls) }.flatten

      map = Hash.new
      files.to_a.each do |fn, ls|
        map[fn] = ls.map { |line| line.contents }
      end

      # Remove first
      [ TeXCheck::MemoryReader.new( map ), errs.sort ]
    end

  end
  
end
